<?php
class Apps_Plugin_Grid_Search_Abstract extends Apps_Form {
    const RENDER_NORENDER = 'no-render';
    const RENDER_SIMPLE   = 'simple';
    const RENDER_ADVANCED = 'advanced';
    const RENDER_BOTH     = 'both';

    const ADVANCED_KEYWORD     = '>'; // Advanced search start with this keyword
    const ADVANCED_DELIMITER   = ';';
    const ADVANCED_LCDELIMITER = ':';

    const SEARCH_BOX_NAME  = 's_keyword';

    const ELEMENT_TEXT    = 'text';
    const ELEMENT_DATE    = 'date';
    const ELEMENT_SELECT  = 'select';
    const ELEMENT_RADIO   = 'radio';
    const ELEMENT_REGION  = 'region';

    public static $restrictDate = array('/(\d{4}[\/-]\d{2}[\/-]\d{2})/', '/(\d+)/');

    public static $criteriaSimple     = array('' => array('= ?', null));
    public static $criteriaStringLike = array('' => array('LIKE ?', '%%%s%%'));
    public static $criteriaDateRange  = array(
                                            '/=?>=?\s*(\d{4}[\/-]\d{2}[\/-]\d{2})/' => array('>= ?', null),
                                            '/=?<=?\s*(\d{4}[\/-]\d{2}[\/-]\d{2})/' => array('<= ?', null),
                                            '/=?(\d{4}[\/-]\d{2}[\/-]\d{2})/'       => array('= ?', null),
                                         );

    protected $_grid = null;
    protected $_advCriteria = null;
    protected $_splCriteria = null;

    protected $_decoratorForm = array(
            array('decorator' => array('fe' => 'FormElements'),
    			  'options'   => null));

    public function preInit() {}

    public function init() {
        $this->preInit();

        foreach($this->_advCriteria as $name => $criterion) {
            $criterion   = $criterion[2];

            $elementType = isset($criterion['element'])
                           ? $criterion['element']
                           : self::ELEMENT_TEXT;

            $elementLabel= isset($criterion['label'])
                           ? $criterion['label']
                           : $name;

            $elementAttrs= isset($criterion['attribs'])
                           ? $criterion['attribs']
                           : array();

            $criterionMultiOptions= isset($criterion['multiOptions'])
                           ? $criterion['multiOptions']
                           : array();
            $elementMultiOptions = array(''=>'');
            foreach($criterionMultiOptions as $k=>$v) {
                $elementMultiOptions [$k]= $v;
            }

            $e = null;
            switch($elementType) {
                case self::ELEMENT_DATE:
                    $elementAttrs['class'] = (isset($elementAttrs['class'])
                                              ? $elementAttrs['class']
                                              : '')
                                              . ' search-criterion-select';

                    $e = new Zend_Form_Element_Radio($name);
                    $e->setLabel($elementLabel)
                      ->setAttribs($elementAttrs);

                    $this->addDateRanges($e);

                    break;

                case self::ELEMENT_RADIO:
                    $e = new Zend_Form_Element_Radio($name);
                case self::ELEMENT_SELECT:
                    if(!$e instanceof Zend_Form_Element) {
                        $e = new Zend_Form_Element_Select($name);
                    }

                    $e->setMultiOptions($elementMultiOptions);
                case self::ELEMENT_TEXT:
                    if(!$e instanceof Zend_Form_Element) {
                        $e = new Zend_Form_Element_Text($name);
                    }

                    $elementAttrs['class'] = (isset($elementAttrs['class'])
                                              ? $elementAttrs['class']
                                              : '')
                                             . ' search-criterion';

                    $elementAttrs['data-search-tpl'] = $name . self::ADVANCED_LCDELIMITER;

                    $e->setLabel($elementLabel)
                      ->setAttribs($elementAttrs);

                    break;

                case self::ELEMENT_REGION:
                    break;
            }

            if($e instanceof Zend_Form_Element) {
                $this->addElement($e);
            }
        }
        $this->makeup();
    }

    public function setGrid($grid)               { $this->_grid = $grid; return $this; }
    public function setSplCriteria($splCriteria) { $this->_splCriteria = $splCriteria; return $this;}
    public function setAdvCriteria($advCriteria) { $this->_advCriteria = $advCriteria; return $this;}

    public function getGrid()        { return $this->_grid; }
    public function getSplCriteria() { return $this->_splCriteria; }
    public function getAdvCriteria() { return $this->_advCriteria; }

    public function applyCriteria($grid=null, $criteria=null) {
        // Options
        if(null !== $grid)     { $this->setGrid($grid); }
        if(null !== $criteria) { $this->setCriteria($criteria); }

        // Return on resources failed
        $grid = $this->getGrid();
        if(!($grid && $grid instanceof Apps_Plugin_Grid)) { return $this; }

        $rq = $grid->getRequest();
        if($rq->isPost()) {
            // Populate advanced search form
            $this->populate($rq->getParams());

            $keyword = $rq->getParam(self::SEARCH_BOX_NAME);
            if($keyword) {
                // Catch tools
                $dbAdapter = Zend_Db_Table::getDefaultAdapter();
                $cFilter   = new M_Tool_ContentFilter();

                $grid->disablePagination(); // Should be no paging when do searching
                $data = $grid->getData(); // Zend_Db_Select

                $w    = array();          // WHERE condition

                if(self::ADVANCED_KEYWORD == $keyword[0]) {
                // Advanced
                    $criteria   = $this->getAdvCriteria();

                    $keyword    = substr($keyword, 1); // Remove ADVANCED_KEYWORD
                    $conditions = explode(self::ADVANCED_DELIMITER, $keyword);

                    $filteredConditions = array();

                    $lcDelimiter = self::ADVANCED_LCDELIMITER; // Delimiter tween search-condition label&content

                    foreach($conditions as $condition) {
                        // Filter keysearch
                        if(false === strpos($condition, $lcDelimiter)) { continue; } // Skip on malformed
                        $condition = explode($lcDelimiter, $condition, 2);

                        // Extract search label - skip on non-specified
                        // Do normalize on label
                        $label = $condition[0];
                        $label = $cFilter->castViToBoringLower($label);
                        if(!isset($criteria[$label])) { continue; }

                        // Get criterion
                        $criterion = $criteria[$label];

                        // Extract search content - skip on empty
                        $content = trim($condition[1]);
                        if(!$content) {continue;}

                        $field     = $criterion[0];
                        $criterion = $criterion[1];

                        // Step throw criterion'criteria
                        foreach($criterion as $regexp => $filter) {
                            $validated = true;

                            // If Regexp used, then validate it
                            // If failed, then skip this criterion
                            if($regexp) {
                                $validated  = false;

                                $matched = array();
                                if(preg_match($regexp, $content, $matched)) {
                                    $validated  = true;
                                    $content = $matched[1];
                                }
                            }

                            if($validated) {
                                // If quoted, then quoted
                                $kw = $filter[1]
                                      ? sprintf($filter[1], $content)
                                      : $content;

                                if(!isset($filteredConditions[$field])) {
                                    $filteredConditions[$field] = array();
                                }
                                // Split condition into sub-group
                                // For what? dunno
                                $filteredConditions[$field][] = $dbAdapter->quoteInto($field . ' ' . $filter[0], $kw);
                                break;
                            }

                        }
                    }
                    foreach($filteredConditions as $c) {
                        if(is_array($c)) {
                            if(isset($c[1])) {
                                $c = implode(' AND ', $c);
                                $c = '(' . $c . ')';
                            } else {
                                $c = $c[0];
                            }
                        }
                        $w [] = $c;
                    }
                    $w = implode(' AND ', $w);

                } else {
                // Simple
                    $criteria = $this->getSplCriteria();

                    foreach($criteria as $f => $criterion) {
                        // Save keyword (it could be changed each loop)
                        $kw = isset($criterion[1])
                              ? sprintf($criterion[1], $keyword)
                              : $keyword;

                        $validated = true;

                        // Restrict format
                        if(isset($criterion[2]) && $restrict = $criterion[2]) {
                            $validated = false;

                            foreach($restrict as $r) {
                                $matched = array();
                                if(preg_match($r, $kw, $matched)) {
                                    $kw = $matched[1];
                                    $validated = true;
                                    break;
                                }
                            }
                        }

                        if($validated) {
                            $w []= $dbAdapter->quoteInto($criterion[0], $kw);
                        }
                    }
                    $w = implode(' OR ', $w);
                }

                if($w) { $data->where($w); }
            }
        }
        return $this;
    }

    // HELPER
    public function addDateRanges($elements) {
        if($elements instanceof Zend_Form_Element) { $elements = array($elements); }
        if(!is_array($elements)) {
            throw new Zend_Exception('Date-range element must be specified by a single Element or an Array of elements');
        }

        $rq = Zend_Controller_Front::getInstance()->getRequest();
        foreach ($elements as $e) {
            $name = $e->getName();

            $decs = array('ViewHelper');
            $attr = array('class' => 'datepicker search-criterion search-criterion-disabled');

            // Create sub-range element
            $ea = new Zend_Form_Element_Text($eaName = ($name . '_actual'));
            $es = new Zend_Form_Element_Text($esName = ($name . '_start'));
            $ee = new Zend_Form_Element_Text($eeName = ($name . '_end'));

            // Set decorator - add to radio
            $attr['data-search-tpl'] = $name . self::ADVANCED_LCDELIMITER;
            $ea->setDecorators($decs)->setAttribs($attr)->setValue($rq->getParam($eaName));

            $attr['data-search-tpl'] = $name . self::ADVANCED_LCDELIMITER . '<=';
            $es->setDecorators($decs)->setAttribs($attr)->setValue($rq->getParam($esName));;

            $attr['data-search-tpl'] = $name . self::ADVANCED_LCDELIMITER . '>=';
            $ee->setDecorators($decs)->setAttribs($attr)->setValue($rq->getParam($eeName));;

            $e->setOptions(array('escape' => false, 'label_class' => 'radio date-group'))
              ->setMultiOptions(array($ea . '', $es . ' <span style="float:left;margin:0 5px">-</span> '. $ee))
              ->setSeparator('<div class="clear"></div>')
              ->setValue(0);

            // Remove decorator - add to form
            $ea->setDecorators(array())->setDisableLoadDefaultDecorators(true);
            $es->setDecorators(array())->setDisableLoadDefaultDecorators(true);
            $ee->setDecorators(array())->setDisableLoadDefaultDecorators(true);

            $this->addElements(array($ea, $es, $ee));
        }

        return $this;
    }

    public function getXhtml($renderType=self::RENDER_BOTH) {
        $xhtml = $renderType==self::RENDER_NORENDER
                 ? ''
                 : ($this->_grid
                    ? $this->_grid->getView()->setScriptPath(dirname(__FILE__))
                                 ->partial('search-box.phtml',
                                     array('form'         => $this,
                                     	   'grid'         => $this->_grid,
                                     	   'renderType'   => $renderType,
                                           'advKeyword'   => self::ADVANCED_KEYWORD,
                                           'advDelimiter' => self::ADVANCED_DELIMITER))
                    : ''
                   );
        return $xhtml;
    }
}